<template>
  <div class="frame-player-container">
    <!-- 使用canvas显示当前帧 -->
    <canvas ref="frameCanvas" :height="canvasHeight" :style="canvasStyle" :width="canvasWidth" class="frame-canvas" />
    <!-- 控制按钮 -->
    <div v-if="showControls && hasImages && imageSources.length > 0"
      :class="{ 'controls-inside': controlsPosition === 'inside', 'controls-outside': controlsPosition === 'outside' }"
      class="controls">
      <button @click="play">播放</button>
      <button @click="pause">暂停</button>
      <button @click="stop">停止</button>
      <button @click="togglePlayDirection">{{ playDirection === 1 ? '倒放' : '正放' }}</button>
      <button @click="toggleLoop">{{ isLooping ? '不循环' : '循环' }}</button>
      <input v-model="currentFrame" :max="totalFrames - 1" :min="0" type="range" @input="onFrameSliderChange" />
      <span>{{ currentFrame + 1 }} / {{ totalFrames }}</span>
    </div>
  </div>
</template>

<script setup>
import {computed, nextTick, onMounted, onUnmounted, ref, watch} from 'vue'
import {convertToResponsive, responsiveToPixels} from '@/utils/responsiveUtils.js'

// 定义props
const props = defineProps({
  // 图片显示模式：visible/opacity
  imageMode: {
    type: String,
    default: 'visible'
  },
  // 播放模式：normal/loop/yoyo
  playMode: {
    type: String,
    default: 'loop'
  },
  // 播放帧率
  fps: {
    type: Number,
    default: 25
  },
  // 播放帧间隔
  playStep: {
    type: Number,
    default: 1
  },
  // 播放速度
  playSpeed: {
    type: Number,
    default: 1
  },
  // 是否自动播放
  autoplay: {
    type: Boolean,
    default: true
  },
  // 是否预加载
  preload: {
    type: Boolean,
    default: true
  },
  // 图片源数组或函数
  images: {
    type: [Array, Function, String],
    default: null
  },
  // 图片总数
  length: {
    type: Number,
    default: 126
  },
  // 是否显示控制按钮
  showControls: {
    type: Boolean,
    default: false
  },
  // 控制栏位置：inside（图片内部）或outside（图片外部）
  controlsPosition: {
    type: String,
    default: 'inside'
  },
  // 图片宽度
  width: {
    type: [String, Number],
    default: 'auto'
  },
  // 图片高度
  height: {
    type: [String, Number],
    default: 'auto'
  },
  // 图片缩放倍数
  scale: {
    type: Number,
    default: 1
  },
  // 是否自动适应图片尺寸
  autoSize: {
    type: Boolean,
    default: true
  },
  // JSON文件路径，用于获取图片地址
  jsonPath: {
    type: String,
    default: ''
  }
})

// 定义emit事件
const emit = defineEmits([
  'play',    // 播放事件
  'pause',   // 暂停事件
  'stop',    // 停止事件
  'ended',   // 播放结束事件
  'loop',    // 循环播放事件
  'yoyo',    // 来回播放事件
  'update'   // 帧更新事件
])


// 从JSON文件获取图片地址
// 异步获取指定JSON文件中的图片地址数组
// @param {string} jsonPath - JSON文件路径，可选参数，默认使用props.jsonPath
// @returns {Promise<boolean>} - 返回是否成功获取图片地址
const fetchImagesFromJson = async (jsonPath = null) => {
  const pathToUse = jsonPath || props.jsonPath
  if (!pathToUse) {
    console.warn('未提供JSON文件路径')
    return false
  }

  try {
    const response = await fetch(pathToUse)
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }
    const data = await response.json()
    if (data.images && Array.isArray(data.images) && data.images.length > 0) {
      jsonImages.value = data.images
      hasImages.value = true
      return true
    } else {
      console.warn('JSON文件中未找到有效的图片地址数组')
      return false
    }
  } catch (error) {
    console.error('Error fetching images from JSON:', error)
    return false
  }
}

// 组件状态
const frameCanvas = ref(null)
const canvasContext = ref(null)
const isPlaying = ref(false)
const isLooping = ref(props.playMode === 'loop' || props.playMode === 'yoyo')
const playDirection = ref(1) // 1为正序，-1为倒序
const currentFrame = ref(0)
const imageLoaded = ref(false)
const animationTimer = ref(null)
const preloadedImages = ref([])
const jsonImages = ref([]) // 存储从JSON文件获取的图片地址
const hasImages = ref(false) // 标记是否有可用的图片地址
const canvasWidth = ref(0)
const canvasHeight = ref(0)
const imageNaturalWidth = ref(0) // 图片原始宽度
const imageNaturalHeight = ref(0) // 图片原始高度

// 计算属性 - 图片源数组
// 优先级：JSON文件 > props.images > 空数组
const imageSources = computed(() => {
  // 优先使用从JSON文件获取的图片地址
  if (jsonImages.value.length > 0) {
    return jsonImages.value
  }
  // 其次使用props中传入的图片地址
  if (props.images) {
    return props.images
  }
  // 如果既没有JSON文件也没有传入图片地址，返回空数组
  return []
})

// 计算属性 - 总帧数
// 返回当前可用的图片总数
const totalFrames = computed(() => {
  return imageSources.value.length
})


// 计算属性 - Canvas样式
// 计算canvas元素的样式，控制其尺寸和显示状态
const canvasStyle = computed(() => {
  let width, height

  // 如果启用了自动尺寸，并且已经加载了图片，则使用图片的原始尺寸
  if (props.autoSize && imageNaturalWidth.value > 0 && imageNaturalHeight.value > 0) {
    const actualWidth = imageNaturalWidth.value * props.scale
    const actualHeight = imageNaturalHeight.value * props.scale
    
    // 使用通用方法转换为响应式值
    const responsiveSize = convertToResponsive(actualWidth, actualHeight)
    width = responsiveSize.width
    height = responsiveSize.height
  } else {
    // 否则使用props中指定的尺寸
    const baseWidth = typeof props.width === 'number' ? props.width : parseInt(props.width) || 800
    const baseHeight = typeof props.height === 'number' ? props.height : parseInt(props.height) || 600
    const actualWidth = baseWidth * props.scale
    const actualHeight = baseHeight * props.scale
    
    // 使用通用方法转换为响应式值
    const responsiveSize = convertToResponsive(actualWidth, actualHeight)
    width = responsiveSize.width
    height = responsiveSize.height
  }

  return {
    width,
    height,
    opacity: props.imageMode === 'opacity' ? (imageLoaded.value ? 1 : 0) : 1,
    display: props.imageMode === 'visible' ? (imageLoaded.value ? 'block' : 'none') : 'block'
  }
})

// 计算属性 - 帧间隔时间（毫秒）
// 根据帧率和播放速度计算每帧的显示时间
const frameInterval = computed(() => {
  return (1000 / props.fps) / props.playSpeed
})


// 初始化Canvas
// 设置canvas的上下文和尺寸，为后续绘制做准备
const initCanvas = () => {
  if (!frameCanvas.value) return

  canvasContext.value = frameCanvas.value.getContext('2d')

  let width, height

  // 如果启用了自动尺寸，并且已经加载了图片，则使用图片的原始尺寸
  if (props.autoSize && imageNaturalWidth.value > 0 && imageNaturalHeight.value > 0) {
    width = imageNaturalWidth.value
    height = imageNaturalHeight.value
  } else {
    // 否则使用props中指定的尺寸
    width = typeof props.width === 'number' ? props.width : parseInt(props.width) || 800
    height = typeof props.height === 'number' ? props.height : parseInt(props.height) || 600
  }

  // 应用缩放倍数
  width = width * props.scale
  height = height * props.scale

  // 将vw/vh转换为实际像素值
  const responsiveSize = convertToResponsive(width, height)
  const pixelSize = responsiveToPixels(responsiveSize.width, responsiveSize.height)
  
  canvasWidth.value = pixelSize.width
  canvasHeight.value = pixelSize.height

  frameCanvas.value.width = pixelSize.width
  frameCanvas.value.height = pixelSize.height
}

// 渲染当前帧到Canvas
// 将当前帧对应的图片绘制到canvas上
// 这是canvas实现序列帧播放的核心方法
const renderFrameToCanvas = () => {
  if (!canvasContext.value || !preloadedImages.value.length || currentFrame.value >= preloadedImages.value.length) {
    return
  }

  const img = preloadedImages.value[currentFrame.value]
  if (!img || !img.complete) return

  // 清除画布
  canvasContext.value.clearRect(0, 0, canvasWidth.value, canvasHeight.value)

  // 如果启用了保持宽高比，则按比例缩放图片
  if (props.keepAspectRatio && imageNaturalWidth.value > 0 && imageNaturalHeight.value > 0) {
    // 计算图片在canvas中的显示区域，保持宽高比
    const imgRatio = imageNaturalWidth.value / imageNaturalHeight.value
    const canvasRatio = canvasWidth.value / canvasHeight.value
    
    let drawWidth, drawHeight, drawX, drawY
    
    if (imgRatio > canvasRatio) {
      // 图片比canvas更宽，以canvas宽度为准
      drawWidth = canvasWidth.value
      drawHeight = canvasWidth.value / imgRatio
      drawX = 0
      drawY = (canvasHeight.value - drawHeight) / 2
    } else {
      // 图片比canvas更高，以canvas高度为准
      drawHeight = canvasHeight.value
      drawWidth = canvasHeight.value * imgRatio
      drawX = (canvasWidth.value - drawWidth) / 2
      drawY = 0
    }
    
    // 绘制图片，保持宽高比
    canvasContext.value.drawImage(img, drawX, drawY, drawWidth, drawHeight)
  } else {
    // 不保持宽高比，直接拉伸填充整个canvas
    canvasContext.value.drawImage(img, 0, 0, canvasWidth.value, canvasHeight.value)
  }
}

// 加载第一张图片获取尺寸
// 在非预加载模式下，加载第一张图片以获取其原始尺寸
const loadFirstImageForSize = () => {
  if (!hasImages.value || imageSources.value.length === 0) {
    console.warn('lomoFramePlayer: 没有可用的图片地址，无法获取尺寸')
    return
  }

  const img = new Image()
  img.crossOrigin = 'anonymous' // 解决跨域问题
  img.onload = () => {
    if (props.autoSize) {
      imageNaturalWidth.value = img.naturalWidth
      imageNaturalHeight.value = img.naturalHeight
      // 重新初始化Canvas以应用新的尺寸
      initCanvas()
    }
    // 将第一张图片添加到预加载数组中
    preloadedImages.value[0] = img
    // 渲染第一帧
    renderFrameToCanvas()
    // 如果设置为自动播放，则开始播放
    if (props.autoplay) {
      play()
    }
  }
  img.onerror = () => {
    console.error(`Failed to load first image: ${imageSources.value[0]}`)
    // 即使加载失败，也初始化Canvas
    initCanvas()
  }
  img.src = imageSources.value[0]
}

// 预加载图片
// 提前加载所有图片到内存，提高播放时的性能
// 加载完成后初始化canvas并渲染第一帧
const preloadImages = () => {
  if (!props.preload) return
  if (!hasImages.value || imageSources.value.length === 0) {
    console.warn('lomoFramePlayer: 没有可用的图片地址，无法预加载')
    return
  }

  preloadedImages.value = []
  let loadedCount = 0

  imageSources.value.forEach((src, index) => {
    const img = new Image()
    img.crossOrigin = 'anonymous' // 解决跨域问题
    img.onload = () => {
      // 第一张图片加载完成后，获取其原始尺寸
      if (index === 0 && props.autoSize) {
        imageNaturalWidth.value = img.naturalWidth
        imageNaturalHeight.value = img.naturalHeight
      }
      
      loadedCount++
      if (loadedCount === imageSources.value.length) {
        imageLoaded.value = true
        // 初始化Canvas并渲染第一帧
        initCanvas()
        renderFrameToCanvas()
        // 如果设置为自动播放，则开始播放
        if (props.autoplay) {
          play()
        }
      }
    }
    img.onerror = () => {
      console.error(`Failed to load image: ${src}`)
      loadedCount++
      if (loadedCount === imageSources.value.length) {
        imageLoaded.value = true
        initCanvas()
        renderFrameToCanvas()
        // 如果设置为自动播放，则开始播放
        if (props.autoplay) {
          play()
        }
      }
    }
    img.src = src
    preloadedImages.value.push(img)
  })
}

// 播放动画
// 开始序列帧动画播放，设置定时器定期更新帧
const play = () => {
  if (!hasImages.value) {
    console.warn('lomoFramePlayer: 没有可用的图片地址，无法播放')
    return
  }

  if (isPlaying.value) return

  isPlaying.value = true
  emit('play')

  // 清除之前的定时器
  if (animationTimer.value) {
    clearInterval(animationTimer.value)
  }

  // 设置新的定时器
  animationTimer.value = setInterval(() => {
    updateFrame()
  }, frameInterval.value)
}

// 暂停动画
// 暂停序列帧动画播放，清除定时器
const pause = () => {
  if (!hasImages.value || imageSources.value.length === 0) {
    console.warn('lomoFramePlayer: 没有可用的图片地址，无法暂停')
    return
  }

  if (!isPlaying.value) return

  isPlaying.value = false
  emit('pause')

  if (animationTimer.value) {
    clearInterval(animationTimer.value)
    animationTimer.value = null
  }
}

// 停止动画
// 停止序列帧动画播放并重置到第一帧
const stop = () => {
  if (!hasImages.value || imageSources.value.length === 0) {
    console.warn('lomoFramePlayer: 没有可用的图片地址，无法停止')
    return
  }

  pause()
  currentFrame.value = 0
  emit('stop')
}

// 重新播放
// 停止当前播放并重新开始播放
const replay = () => {
  if (!hasImages.value || imageSources.value.length === 0) {
    console.warn('lomoFramePlayer: 没有可用的图片地址，无法重播')
    return
  }
  stop()
  play()
}

// 切换播放方向
// 在正序播放和倒序播放之间切换
const togglePlayDirection = () => {
  if (!hasImages.value || imageSources.value.length === 0) {
    console.warn('lomoFramePlayer: 没有可用的图片地址，无法切换播放方向')
    return
  }

  playDirection.value = playDirection.value === 1 ? -1 : 1
  emit('direction-change', playDirection.value)
}

// 切换循环模式
// 在循环播放和非循环播放之间切换
const toggleLoop = () => {
  isLooping.value = !isLooping.value
}

// 更新帧
// 根据播放模式和方向更新当前帧，并处理边界情况
// 这是动画播放的核心逻辑，处理各种播放模式和边界条件
const updateFrame = () => {
  if (!hasImages.value || imageSources.value.length === 0) {
    console.warn('lomoFramePlayer: 没有可用的图片地址，无法更新帧')
    return
  }

  // 更新当前帧
  currentFrame.value += props.playStep * playDirection.value

  // 检查边界
  if (currentFrame.value >= totalFrames.value) {
    if (isLooping.value) {
      if (props.playMode === 'yoyo') {
        // 来回播放模式，到达末尾后反向
        playDirection.value = -1
        currentFrame.value = totalFrames.value - 1
        emit('yoyo', currentFrame.value)
      } else {
        // 循环播放模式，回到开头
        currentFrame.value = 0
        emit('loop',)
      }
    } else {
      // 不循环，停止播放
      currentFrame.value = totalFrames.value - 1
      pause()
      emit('ended')
    }
  } else if (currentFrame.value < 0) {
    if (isLooping.value && props.playMode === 'yoyo') {
      // 来回播放模式，到达开头后正向
      playDirection.value = 1
      currentFrame.value = 0
      emit('yoyo', currentFrame.value)
    } else {
      // 其他情况，回到开头
      currentFrame.value = 0
      if (!isLooping.value) {
        pause()
        emit('ended')
      }
    }
  }

  // 渲染当前帧到Canvas
  renderFrameToCanvas()

  // 发送帧更新事件
  emit('update', currentFrame.value)
}

// 滑块改变帧
// 处理用户通过滑块改变帧的事件
const onFrameSliderChange = () => {
  emit('update', currentFrame.value)
}

// 跳转到指定帧
// 直接跳转到指定的帧并渲染
// @param {number} frame - 要跳转到的帧索引
const gotoFrame = (frame) => {
  if (!hasImages.value || imageSources.value.length === 0) {
    console.warn('lomoFramePlayer: 没有可用的图片地址，无法跳转帧')
    return
  }

  if (frame >= 0 && frame < totalFrames.value) {
    currentFrame.value = frame
    // 渲染当前帧到Canvas
    renderFrameToCanvas()
    emit('update', currentFrame.value)
  }
}

// 下一帧
// 切换到下一帧，如果是最后一帧且为循环模式则回到第一帧
const nextFrame = () => {
  if (!hasImages.value || imageSources.value.length === 0) {
    console.warn('lomoFramePlayer: 没有可用的图片地址，无法切换到下一帧')
    return
  }

  if (currentFrame.value < imageSources.value.length - 1) {
    gotoFrame(currentFrame.value + 1)
  } else if (props.playMode === 'loop') {
    gotoFrame(0)
  }
}

// 上一帧
// 切换到上一帧，如果是第一帧且为循环模式则回到最后一帧
const prevFrame = () => {
  if (!hasImages.value || imageSources.value.length === 0) {
    console.warn('lomoFramePlayer: 没有可用的图片地址，无法切换到上一帧')
    return
  }

  if (currentFrame.value > 0) {
    gotoFrame(currentFrame.value - 1)
  } else if (props.playMode === 'loop') {
    gotoFrame(imageSources.value.length - 1)
  }
}

// 设置播放器参数
// 动态修改播放器的各种参数
// @param {string} attr - 参数名称
// @param {*} value - 参数值
const setParam = (attr, value) => {
  switch (attr) {
    case 'fps':
      props.fps = value
      break
    case 'playSpeed':
      props.playSpeed = value
      break
    case 'playStep':
      props.playStep = value
      break
    case 'playMode':
      props.playMode = value
      isLooping.value = value === 'loop' || value === 'yoyo'
      break
    case 'imageMode':
      props.imageMode = value
      break
  }

  // 如果正在播放，重新启动定时器以应用新的帧率
  if (isPlaying.value) {
    pause()
    play()
  }
}

// 重新加载JSON文件中的图片地址
// 从指定的JSON文件重新加载图片地址并重新初始化播放器
// @param {string} jsonPath - JSON文件路径，可选参数，默认使用props.jsonPath
// @returns {Promise<boolean>} - 返回是否成功重新加载
const reloadImagesFromJson = async (jsonPath = null) => {
  const pathToUse = jsonPath || props.jsonPath
  if (!pathToUse) {
    console.warn('未提供JSON文件路径，无法重新加载图片')
    return false
  }

  // 停止当前播放
  if (isPlaying.value) {
    pause()
  }

  // 重置状态
  jsonImages.value = []
  hasImages.value = false
  imageLoaded.value = false

  // 重新获取图片地址
  const success = await fetchImagesFromJson(pathToUse)

  if (success) {
    // 重新预加载图片
    preloadImages()
    // 重置到第一帧
    currentFrame.value = 0
    // 如果设置为自动播放，则开始播放
    if (props.autoplay) {
      play()
    }
    return true
  } else {
    console.warn('重新加载图片失败')
    return false
  }
}

// 从指定JSON文件路径加载图片
// 加载指定路径的JSON文件中的图片地址
// @param {string} jsonPath - JSON文件路径
// @returns {Promise<boolean>} - 返回是否成功加载
const loadImagesFromJsonPath = async (jsonPath) => {
  if (!jsonPath) {
    console.warn('未提供JSON文件路径，无法加载图片')
    return false
  }

  return await reloadImagesFromJson(jsonPath)
}

// 监听props变化
// 监听播放模式变化并更新循环状态
watch(() => props.playMode, (newMode) => {
  isLooping.value = newMode === 'loop' || newMode === 'yoyo'
})

// 组件挂载时初始化
// 组件挂载时检查图片来源并进行初始化
onMounted(async () => {
  let imagesLoaded = false

  // 如果提供了JSON文件路径，尝试获取图片地址
  if (props.jsonPath) {
    imagesLoaded = await fetchImagesFromJson()
    // 确保hasImages正确设置
    if (imagesLoaded && !hasImages.value) {
      hasImages.value = true
    }
  }

  // 如果没有从JSON文件获取到图片地址，检查props中是否传入了图片地址
  if (!imagesLoaded && props.images) {
    if (Array.isArray(props.images) && props.images.length > 0) {
      hasImages.value = true
      imagesLoaded = true
    } else {
      console.warn('props中传入的images不是有效的数组')
    }
  }

  // 如果没有可用的图片地址，输出警告
  if (!hasImages.value) {
    console.warn('lomoFramePlayer: 未提供有效的图片地址，请通过jsonPath属性指定JSON文件路径或通过images属性传入图片地址数组')
  }

  // 初始化Canvas
  nextTick(() => {
    initCanvas()
  })

  // 如果有可用的图片地址
  if (hasImages.value) {
    if (props.preload) {
      // 预加载图片
      preloadImages()
    } else {
      // 非预加载情况下，加载第一张图片获取尺寸
      loadFirstImageForSize()
    }
  }
})



// 组件卸载时清理
// 清理定时器和预加载的图片资源
onUnmounted(() => {
  if (animationTimer.value) {
    clearInterval(animationTimer.value)
    animationTimer.value = null
  }

  // 清理预加载的图片
  if (hasImages.value) {
    preloadedImages.value = []
  }
  

})


// 暴露方法给父组件
defineExpose({
  play,
  pause,
  stop,
  replay,
  gotoFrame,
  nextFrame,
  prevFrame,
  setParam,
  togglePlayDirection,
  toggleLoop,
  reloadImagesFromJson,
  loadImagesFromJsonPath,
})
</script>

<style scoped>
/* 播放器容器样式 */
.frame-player-container {
  position: relative;
  overflow: hidden;
  /* 防止内容溢出 */
}

/* Canvas元素样式 - 优化性能和显示效果 */
.frame-canvas {
  display: block;
  max-width: 100%;
  max-height: 100%;
  /* 添加以下属性以提高图像渲染质量 */
  image-rendering: -webkit-optimize-contrast;
  image-rendering: crisp-edges;
  image-rendering: pixelated;
  /* 禁用图像平滑以提高性能 */
  image-rendering: -moz-crisp-edges;
  image-rendering: -webkit-crisp-edges;
  image-rendering: pixelated;
  /* 添加过渡效果使帧切换更平滑 */
  transition: opacity 0.1s ease-in-out;
}

/* 控制栏基础样式 - 优化布局和交互 */
.controls {
  position: absolute;
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
  padding: 10px;
  background-color: rgba(0, 0, 0, 0.7);
  /* 增加不透明度提高可读性 */
  border-radius: 5px;
  color: white;
  /* 添加过渡效果 */
  transition: all 0.3s ease;
  /* 添加阴影提高层次感 */
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
  /* 防止控制栏被选中 */
  user-select: none;
  /* 提高控制栏的z-index确保显示在最上层 */
  z-index: 10;
}

/* 控制栏内部位置样式 */
.controls-inside {
  bottom: 10px;
  /* 添加渐变效果使控制栏与图像融合 */
  background: linear-gradient(to top, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.6));
}

/* 控制栏外部位置样式 */
.controls-outside {
  bottom: -50px;
}

/* 控制按钮样式 - 优化交互效果 */
.controls button {
  padding: 5px 10px;
  background-color: #4CAF50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  /* 添加过渡效果 */
  transition: all 0.2s ease;
  /* 防止按钮被选中 */
  user-select: none;
  /* 添加最小宽度确保按钮大小一致 */
  min-width: 60px;
  /* 添加字体大小 */
  font-size: 12px;
}



.no-images-message p {
  margin: 0;
  padding: 10px;
  background-color: rgba(255, 255, 255, 0.8);
  border-radius: 4px;
}

/* 控制按钮悬停样式 - 增强视觉反馈 */
.controls button:hover {
  background-color: #45a049;
  /* 添加轻微缩放效果 */
  transform: scale(1.05);
}

/* 控制按钮激活状态样式 */
.controls button:active {
  transform: scale(0.95);
}

/* 帧滑块样式 - 优化外观和交互 */
.controls input[type="range"] {
  width: 150px;
  /* 自定义滑块样式 */
  -webkit-appearance: none;
  appearance: none;
  height: 5px;
  background: rgba(255, 255, 255, 0.3);
  border-radius: 5px;
  outline: none;
}

/* 滑块手柄样式 */
.controls input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 15px;
  height: 15px;
  background: #4CAF50;
  border-radius: 50%;
  cursor: pointer;
  transition: all 0.2s ease;
}

/* 滑块手柄悬停样式 */
.controls input[type="range"]::-webkit-slider-thumb:hover {
  background: #45a049;
  transform: scale(1.2);
}

/* 帧计数器样式 */
.controls span {
  font-size: 12px;
  min-width: 60px;
  text-align: center;
  /* 添加背景使文本更易读 */
  background-color: rgba(0, 0, 0, 0.5);
  padding: 3px 6px;
  border-radius: 3px;
}
</style>